﻿module Logger.Main

open Casanova
open Casanova.Core
open Casanova.Utilities
open Casanova.Coroutines
open Casanova.Game
open Casanova.Math
open Casanova.Drawing
open Casanova.StandardLibrary.Core
open Casanova.StandardLibrary.Physics
open VirtualCity.World
open VirtualCity.Main
open VirtualCity.CityDescription
open VirtualCity.City
open VirtualCity.PlanDescription
open VirtualCity.Plan
open VirtualCity.Scripts
open VirtualCity.Scripts.Planner


let private create_agents ai_name num_reference_AIs num_external_AIs (world:World) = 
  let num_homes = world.City.Buildings |> Seq.filter (fun b -> (!b.Caption.String).Contains("Home")) |> Seq.length
  let workplaces = world.City.Buildings |> Seq.filter (fun b -> !b.Items |> Seq.exists (fun i -> i.UseEffects |> Seq.exists (fun use_effect -> use_effect.MoneyGained > 0.0f<eur>))) |> Seq.toList
  let external_AI = StudentsAssignment.MovesSelector.SelectMoves
  let num_reference_AIs = min num_reference_AIs num_homes
  let num_external_AIs = min num_external_AIs (num_homes - num_reference_AIs)
  let ai_random = System.Random()
  co{
    for i,home in world.City.Buildings |> Seq.filter (fun b -> (!b.Caption.String).Contains("Home")) |> Seq.mapi (fun x y -> x,y) |> Seq.skip num_reference_AIs |> Seq.take num_external_AIs do
      let workplaces = workplaces |> List.sortBy (fun wp -> Vector2.Distance(wp.Base.Position, home.Base.Position)) 
      let workplaces = workplaces |> Seq.take (max 1 (workplaces.Length / 4)) |> Seq.toList
      let work = workplaces |> List.RandomElement
      let ai_gender = ai_random.Next(2)
      let workname = work.Caption.String.GetValue
      let jobrank = if(workname="Hospital")then "Dr. " else ""
      let agent = 
        let plan =
          {
            PlanDescription.Steps = []
          }
        {
          Name          = if(i-num_reference_AIs=0)then jobrank+"Jendrik" else if(i-num_reference_AIs=1)then jobrank+"Bart" else jobrank+ai_name + (i-num_reference_AIs-1).ToString()
       //   Visual        = DrawableSprite.Create(world.Layers.Agents, Vector2.Zero, Vector2.One * 0.2f, @"Characters\me", Color.Azure)
          Visual        = if(i-num_reference_AIs=0)then DrawableSprite.Create(world.Layers.Agents, Vector2.Zero, Vector2.One * 0.2f, @"Characters\Jendrik", Color.Azure) else
                            if(i-num_reference_AIs=1)then DrawableSprite.Create(world.Layers.Agents, Vector2.Zero, Vector2.One * 0.2f, @"Characters\Bart", Color.Azure) else
                            if(ai_gender=1) then DrawableSprite.Create(world.Layers.Agents, Vector2.Zero, Vector2.One * 0.2f, @"Characters\me", Color.Azure) else
                            DrawableSprite.Create(world.Layers.Agents, Vector2.Zero, Vector2.One * 0.2f, @"Characters\mef", Color.Azure)
          VisualOffset  = Vector2.Create(random_range -10.0f 10.0f, random_range -10.0f 10.0f) * 2.0f
          Physical      = PhysicalEntity.Create(home.Base.Position, 0.0f<m/s>)
          Home          = ref home
          Work          = ref work
          Stats         = Rule.Create(fun () -> Stats.One)
          Money         = var 20.0f<eur>
          ActivePlan    = var plan
          CurrentAction = var ""
          Selected      = var false
          DebugLog      = var ""
          StatsIdleMask = var Stats.One
        }
      world.City.Agents := agent :: !world.City.Agents
      do run_ai_script
           (co{
              do! wait (random_range 600.0f<s> 1800.0f<s>)
              let rec agent_planner() =
                co{
                  if (!agent.Stats).VitalMin <= 0.0f then
                    printf "Agent %s died with stats %s. His plan was \n%s\n.Bumping him up." agent.Name ((!agent.Stats).ToString()) ((!agent.ActivePlan).ToString())
//                      agent.Stats := !agent.Stats + Stats.One
                  else
                    let agent_description = agent |> get_agent_description
                    let city_description = world.City |> get_city_description
                    let plan = external_AI(city_description,agent_description)
                    agent.ActivePlan :=
                      {
                        Steps = 
                          [
                            for step in plan do
                              yield
                                {
                                  Expectations = { City = city_description; Agent = agent_description }
                                  Action       = step
                                }
                          ]
                      }
                    let! res = PlanExecutor.follow_plan false true world.City agent
                    do! yield_
                    do! agent_planner()
                }
              do! agent_planner()
            })
  }


/// Launch the virtual city simulation
let run(num_reference_AIs, num_ais, ai_name, log_file:string) = 
  let log_file = new System.IO.StreamWriter(log_file)
  let logger (world:World) = 
       co{
          do! wait 60.0f<s>
          do log_file.WriteLine("Logging for time: " + (!world.City.Clock.Time).ToString())
          for agent in !world.City.Agents do
            let s = sprintf "Agent %s has stats %s and is at position %s." 
                            agent.Name 
                            ((!agent.Stats).ToString())
                            ((!agent.Physical.Position).ToString())
            do log_file.WriteLine(s)
          do log_file.WriteLine()
          do log_file.WriteLine("##########")
          do log_file.WriteLine()
          do log_file.Flush()
          do! wait 3600.0f<s>
        } |> repeat_

  let start_game (device,content,exit) =
    let exit () = 
      log_file.Close()
      exit()
    start_game num_reference_AIs false (fun world -> logger world .&&> (create_agents ai_name num_reference_AIs num_ais world)) (device,content,exit)
  Game<World>.Create(start_game, 1280, 800).Run()
